﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using System.ComponentModel;

namespace Sequence
{
    /// <summary>
    /// Interaction logic for SequenceGraph.xaml
    /// </summary>
    public partial class SequenceGraph : UserControl
    {
        private CollectionViewSource _SequenceSteps;
        private Point _startPos;
        private bool bUpdatePositions = false;
        private Ellipse _topCircle;
        private UIElement _animatedElement = null;
        private SalesManagerEntities _context;
        private int _ownerID;

        public SequenceGraph()
        {
            InitializeComponent();
        }

        public void Initialize(SalesManagerEntities context, int ownerID)
        {
            _context = context;
            _ownerID = ownerID;
        }

        private void UserControl_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            _SequenceSteps = (CollectionViewSource)e.NewValue;
        }

        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.Register("ItemsSource", typeof(ListCollectionView), typeof(SequenceGraph), new PropertyMetadata(ItemsSourceChangedCallback));
        public ListCollectionView ItemsSource
        {
            get { return (ListCollectionView)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        private static void ItemsSourceChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            ((SequenceGraph)obj).UpdateCanvas();
        }

        public void UpdateCanvas()
        {
            canvas1.Children.Clear();

            _topCircle = new Ellipse();
            _topCircle.Stroke = Brushes.Black;
            _topCircle.Width = 20;
            _topCircle.Height = 20;
            _topCircle.StrokeThickness = 2;
            canvas1.Children.Add(_topCircle);
            Canvas.SetLeft(_topCircle, 200);
            Canvas.SetTop(_topCircle, 0);
            Canvas.SetZIndex(_topCircle, 3);

            ListCollectionView view = ItemsSource;
            view.Refresh();

            // Add all of the UI elements
            foreach (tblSequenceStep step in view)
            {
                SequenceStep stepControl = new SequenceStep(_context, _ownerID);
                canvas1.Children.Add(stepControl);
                Canvas.SetZIndex(stepControl, 1);
                stepControl.DataContext = step;
                stepControl.OnMove += new EventHandler<SequenceStepMouseMoveEventArgs>(stepControl_OnMove);
                stepControl.OnMoveStart += new EventHandler<SequenceStepMouseMoveEventArgs>(stepControl_OnMoveStart);
                stepControl.OnMoveEnd += new EventHandler<SequenceStepMouseMoveEventArgs>(stepControl_OnMoveEnd);
                stepControl.OnRemoveStep += new EventHandler<EventArgs>(stepControl_OnRemoveStep);
                DependencyPropertyDescriptor.FromProperty(Canvas.LeftProperty, typeof(SequenceStep))
                    .AddValueChanged(stepControl, OnStepPositionChanged);
                OnStepPositionChanged(stepControl, null);
                stepControl.SizeChanged += new SizeChangedEventHandler(stepControl_SizeChanged);
                //stepControl.Measure(new Size(this.ActualWidth, this.ActualHeight));

                Line stepLine = new Line();
                stepLine.Stroke = new SolidColorBrush(Colors.Black);
                stepLine.StrokeThickness = 2;
                canvas1.Children.Add(stepLine);
                Canvas.SetZIndex(stepLine, 2);

                Ellipse circle = new Ellipse();
                circle.Stroke = Brushes.Black;
                circle.Width = 20;
                circle.Height = 20;
                circle.StrokeThickness = 2;
                canvas1.Children.Add(circle);
                Canvas.SetZIndex(circle, 3);

                step.UISequenceStep = stepControl;
                step.UILine = stepLine;
                step.UICircle = circle;
            }

            // Trigger the page to layout to default positions
            bUpdatePositions = true;
        }

        void stepControl_OnRemoveStep(object sender, EventArgs e)
        {
            UpdateCanvas();
        }

        void SetPositionAtLevel(CollectionView steps, double top, ref double left, double middle)
        {
            int ct = 0;
            if (steps == null)
                return;

            foreach (tblSequenceStep step in steps)
            {
                SequenceStep stepControl = step.UISequenceStep;

                if (ct++ > 0)
                    left += stepControl.ActualWidth + 20;

                Canvas.SetTop(stepControl, top + 40);
                Canvas.SetLeft(stepControl, left);

                SetPositionAtLevel((CollectionView)CollectionViewSource.GetDefaultView(step.NextSteps),
                    top + 40 + stepControl.ActualHeight, ref left, left + (stepControl.ActualWidth / 2));

                SetLinePos(step);
            }
        }

        void stepControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            FrameworkElement elmt = (FrameworkElement)sender;
            tblSequenceStep step = (tblSequenceStep)elmt.DataContext;

            // Set the position of the line connecting this step with it's parent
            SetLinePos(step);
            // Set the positions of the lines connecting this step with it's children
            foreach (tblSequenceStep step2 in step.NextSteps)
                SetLinePos(step2);

            OnStepPositionChanged(sender, null);
        }

        private void SetLinePos(tblSequenceStep step)
        {
            Line line = step.UILine;

            double top = Canvas.GetTop(step.UISequenceStep);
            double left = Canvas.GetLeft(step.UISequenceStep);

            line.Y1 = double.IsNaN(top) ? 20 : top;
            line.X1 = (double.IsNaN(left) ? 200 : left) + step.UISequenceStep.ActualWidth / 2;

            Ellipse circle = step.UICircle;
            Canvas.SetTop(circle, top + step.UISequenceStep.ActualHeight);
            Canvas.SetLeft(circle, left + (step.UISequenceStep.ActualWidth / 2) - 10);

            if (step.PrevSequenceStep == null)
            {
                line.Y2 = 20;
                line.X2 = 210;
            }
            else
            {
                top = Canvas.GetTop(step.PrevSequenceStep.UISequenceStep);
                left = Canvas.GetLeft(step.PrevSequenceStep.UISequenceStep);
                line.Y2 = (double.IsNaN(top) ? 0 : top) + step.PrevSequenceStep.UISequenceStep.ActualHeight + 20;
                line.X2 = (double.IsNaN(left) ? 0 : left) + step.PrevSequenceStep.UISequenceStep.ActualWidth / 2;
                UIElement uie = (UIElement)line;
            }
        }

        void stepControl_OnMoveEnd(object sender, SequenceStepMouseMoveEventArgs e)
        {
            if (_animatedElement != null)
            {
                tblSequenceStep step = (tblSequenceStep)((SequenceStep)sender).DataContext;

                ListCollectionView view = ItemsSource;

                if (_animatedElement == _topCircle)
                    step.PrevSequenceStep = null;

                // Find the affected Sequence steps.
                foreach (tblSequenceStep testStep in view)
                {
                    if (_animatedElement == testStep.UICircle)
                        step.PrevSequenceStep = testStep;
                    if (_animatedElement == testStep.UILine)
                    {
                        step.PrevSequenceStep = testStep.PrevSequenceStep;
                        testStep.PrevSequenceStep = step;
                    }
                }

                // Turn off the animation
                _animatedElement.BeginAnimation(UIElement.OpacityProperty, null);
                _animatedElement = null;

                // Redraw the canvas.
                UpdateCanvas();
            }
        }

        void stepControl_OnMoveStart(object sender, SequenceStepMouseMoveEventArgs e)
        {
            _startPos = e.Pos;
        }

        void stepControl_OnMove(object sender, SequenceStepMouseMoveEventArgs e)
        {
            // Set the new position of the step based on the mouse position
            Point p = Mouse.GetPosition(canvas1);
            Canvas.SetLeft((UIElement)sender, p.X - _startPos.X);
            Canvas.SetTop((UIElement)sender, p.Y - _startPos.Y);

            tblSequenceStep step = (tblSequenceStep)((SequenceStep)sender).DataContext;

            // Call SizeChanged to update the lines connecting the steps and the size of the canvas.
            stepControl_SizeChanged(sender, null);

            // Check to see if the current position intersects with any elements.
            ListCollectionView view = ItemsSource;

            UIElement elmt = null;
            // Test if it intersects with the top circle
            Rect r = new Rect(200, 0, 20, 20);
            if (r.Contains(p))
                elmt = _topCircle;
            // Test if it intersects with any of the other lines or circles.
            foreach (tblSequenceStep testStep in view)
            {
                if (testStep != step)
                {
                    r = new Rect(new Point(testStep.UILine.X1, testStep.UILine.Y1), new Point(testStep.UILine.X2, testStep.UILine.Y2));
                    r.Inflate(5, 0);
                    if (r.Contains(p))
                        elmt = testStep.UILine;

                    if (step.PrevSequenceStep != testStep)
                    {
                        r = new Rect(new Point(Canvas.GetLeft(testStep.UICircle), Canvas.GetTop(testStep.UICircle)),
                                    new Point(Canvas.GetLeft(testStep.UICircle) + 20, Canvas.GetTop(testStep.UICircle) + 20));
                        if (r.Contains(p))
                            elmt = testStep.UICircle;
                    }
                }
            }

            // If there is a current animation that doesn't match the current position then turn the animation off.
            if (elmt != _animatedElement && _animatedElement != null)
            {
                _animatedElement.BeginAnimation(UIElement.OpacityProperty, null);
                _animatedElement = null;
            }

            // If the position intersects with something other than the steps own line or its parent circle,
            // then animate that element.
            if (elmt != null)
            {
                _animatedElement = elmt;
                DoubleAnimation da = new DoubleAnimation(
                    1, 0, TimeSpan.FromMilliseconds(200));
                da.AutoReverse = true;
                da.RepeatBehavior = RepeatBehavior.Forever;
                _animatedElement.BeginAnimation(UIElement.OpacityProperty, da);
            }

            if (p.Y - scrollViewer1.VerticalOffset < 0)
                scrollViewer1.LineUp();
            if (p.Y - scrollViewer1.VerticalOffset > this.ActualHeight)
                scrollViewer1.LineDown();
            if (p.X - scrollViewer1.HorizontalOffset < 0)
                scrollViewer1.LineLeft();
            if (p.X - scrollViewer1.HorizontalOffset > this.ActualWidth)
                scrollViewer1.LineRight();
        }

        void OnStepPositionChanged(object sender, EventArgs e)
        {
            // Adjust the size of the canvas to contain the new positiion of the step
            if (double.IsNaN(canvas1.Height))
                canvas1.Height = 0;
            if (double.IsNaN(canvas1.Width))
                canvas1.Width = 0;
            canvas1.Height = Math.Max(canvas1.ActualHeight, Canvas.GetTop((UIElement)sender) + ((SequenceStep)sender).ActualHeight);
            canvas1.Width = Math.Max(canvas1.ActualWidth, Canvas.GetLeft((UIElement)sender) + ((SequenceStep)sender).ActualWidth);
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            LayoutUpdated += new EventHandler(SequenceGraph_LayoutUpdated);
        }

        void SequenceGraph_LayoutUpdated(object sender, EventArgs e)
        {
            if (bUpdatePositions)
            {
                ListCollectionView view = ItemsSource;

                double top = 0, left = 0;
                view.Filter = new Predicate<object>(t => (((tblSequenceStep)t).PrevSequenceStep == null));
                SetPositionAtLevel(view, top, ref left, 125);
                view.Filter = null;

                bUpdatePositions = false;
            }
        }
    }
}
